package com.examples.gateway.helper.filter;

import com.examples.gateway.common.CheckState;
import com.examples.gateway.helper.config.GatewayHelperProperties;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang3.StringUtils;
import org.redisson.api.RBucket;
import org.redisson.api.RedissonClient;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import javax.annotation.Resource;
import java.util.concurrent.TimeUnit;

/**
 * 流量重放检查过滤器
 * @author Dai YaLu
 */
@Slf4j
public class ApiReplayFilter extends AbstractGlobalHelperFilter{

    @Resource
    private GatewayHelperProperties properties;

    @Resource
    private RedissonClient redissonClient;

    private final AntPathMatcher matcher = new AntPathMatcher();

    private static final long EFFECTIVE_TIME = 600L;

    private static final String MAGIC = "EG";

    @Override
    public Mono<Void> doFilter(ServerWebExchange exchange, GatewayFilterChain chain) {
        String path = exchange.getRequest().getURI().getPath();
        GatewayHelperProperties.ApiReplay apiReplay = properties.getApiReplay();
        if (apiReplay.isEnabled()) {
            if (apiReplay.getSkipPaths().stream().anyMatch(t -> matcher.match(t, path))) {
                return chain.filter(exchange);
            }
            String reqId= getRequestId(exchange);
            if (!validateReqId(reqId)) {
                log.error("reqId: {} 格式错误", reqId);
                return handleError(exchange, CheckState.REQUEST_REPEAT);
            }
            if (!checkTimeStamp(parseTimestamp(reqId))) {
                log.error("时间戳校验错误");
                return handleError(exchange, CheckState.REQUEST_REPEAT);
            }
            if (!checkNotice(parseNotice(reqId))) {
                log.error("notice校验错误");
                return handleError(exchange, CheckState.REQUEST_REPEAT);
            }
        }
        return chain.filter(exchange);
    }

    /**
     * 获取ReqId，ReqId规范
     * MAGIC.length + 2位长度 + 3位时间 + 14位NOTICE + 4位时间 + 14位NOTICE + 6位时间 + N位随机数 + 2位NOTICE
     * @param exchange exchange
     * @return 获取的ReqId
     */
    private String getRequestId(ServerWebExchange exchange) {
        ServerHttpRequest req = exchange.getRequest();
        return req.getHeaders().getFirst(properties.getApiReplay().getRequestIdKey());
    }

    /**
     * 检查时间戳,如果时间戳在上下10分钟之内成功
     * @param timestamp 时间戳
     * @return 如果在上下10分钟之内返回 {@code true} , 否则返回 {@code false}
     */
    private boolean checkTimeStamp(Long timestamp) {
        if (null == timestamp) {
            return false;
        }
        // 待修改
        long now = System.currentTimeMillis();
        long offset = Math.abs(now - timestamp);
        return offset < EFFECTIVE_TIME * 1000;
    }

    /**
     * 检查随机数
     * @param notice 随机数
     * @return 如果随机数没有使用返回 {@code true}, 否则返回 {@code false}
     */
    private boolean checkNotice(String notice) {
        String key = "GATEWAY:API_REPAY:" + notice;
        RBucket<Boolean> flag = redissonClient.getBucket(key);
        return flag.trySet(true, EFFECTIVE_TIME * 2, TimeUnit.SECONDS);
    }

    @Override
    public int getOrder() {
        return 2;
    }


    /**
     * 生成REQ_ID
     * MAGIC.length + 2位长度 + 3位时间 + 14位NOTICE + 4位时间 + 14位NOTICE + 6位时间 + N位随机数 + 2位NOTICE
     * @param timestamp 时间戳
     * @param uuid      notice随机数使用UUID
     * @return 生成的请求ID
     */
    @SuppressWarnings({"unused"})
    private static String encrypt(long timestamp, String uuid) {
        String timestampStr = String.valueOf(timestamp);
        String randomStr = RandomStringUtils.randomAlphanumeric(2, 10);
        int totalLen = uuid.length() + timestampStr.length() + randomStr.length();
        return MAGIC +
                Integer.toHexString(totalLen) +
                timestampStr.substring(0, 3) +
                uuid.substring(0, 14) +
                timestampStr.substring(3, 7) +
                uuid.substring(14, 28) +
                timestampStr.substring(7) +
                randomStr +
                uuid.substring(28);
    }

    private static boolean validateReqId(String reqId) {
        if (StringUtils.isEmpty(reqId)) {
            return false;
        }
        int minLen = 49;
        if (StringUtils.isEmpty(reqId)) {
            return false;
        }
        if (reqId.length() < minLen) {
            return false;
        }
        int len = Integer.parseInt(reqId.substring(2,4), 16);
        return reqId.length() == len + MAGIC.length() + 2;
    }

    private static Long parseTimestamp(String reqId) {
        String str = reqId.substring(4, 7) + reqId.substring(21, 25) + reqId.substring(39, 45);
        try {
            return Long.parseLong(str);
        } catch (Exception e) {
            return null;
        }

    }

    private static String parseNotice(String reqId) {
        int len = Integer.parseInt(reqId.substring(2,4), 16);
        int noticeLen = len - 32 - 13;
        return reqId.substring(7, 21) + reqId.substring(25, 39) + reqId.substring(45 + noticeLen);
    }
//    public static void main(String[] args) {
//        String uuid = UUID.randomUUID().toString().replace("-", "");
//        long timestamp = System.currentTimeMillis();
//        String reqId = encrypt(timestamp, uuid);
//        System.out.println(reqId);
//        System.out.println(parseTimestamp(reqId).equals(timestamp));
//        System.out.println(uuid.equals(parseNotice(reqId)));
//        System.out.println(validateReqId(reqId));
//    }
}
